Attempting to use tidymodels for processing our IBM krill data and selecting variables of importance for each swimming parameter

library(tidymodels)  # for the parsnip package, along with the rest of tidymodels

# Helper packages
library(readr)       # for importing data
library(broom.mixed) # for converting bayesian models to tidy tibbles
library(dotwhisker)  # for visualizing regression results
library(vip)         # for variable importance plots


rm(list=ls(all=TRUE))   ## removes the previous workspace and environment so that we only have the data we need loaded in the session
load("~/Post-doc/krill-tank-code/DataProcessing/Notebooks/ParameterizationModel.Rdata")


dim(parameters)
glimpse(parameters)

dim(conditions)
glimpse(conditions)

df <- data.frame(conditions[,1],conditions[,2],conditions[,3],conditions[,4],parameters[,1], parameters[,7])
colnames(df) <- c("flow","chl", "guano", "light","ave.v", "dip.test")
df
df<-na.omit(df)


ave.v ~ flow * chl * guano * light


linear_reg()

linear_reg() %>% 
  set_engine("keras")

lm_mod <- linear_reg()

lm_fit <- 
  lm_mod %>% 
  fit(ave.v ~ flow * chl * guano * light, data = df)  ## creates a linear model (LM)
lm_fit

tidy(lm_fit)  ## summary of model


tidy(lm_fit) %>% 
  dwplot(dot_args = list(size = 2, color = "black"),
         whisker_args = list(color = "black"),
         vline = geom_vline(xintercept = 0, colour = "grey50", linetype = 2))

new_points <- expand.grid(flow = 6.1,
                          chl = 19,
                          guano = 0,
                          light = 0) ## create new points
new_points

mean_pred <- predict(lm_fit, new_data = new_points)  ## mean predicted body width
mean_pred

conf_int_pred <- predict(lm_fit, 
                         new_data = new_points, 
                         type = "conf_int")  ## confidence interval prediction
conf_int_pred

plot_data <- 
  new_points %>% 
  bind_cols(mean_pred) %>% 
  bind_cols(conf_int_pred)

# and plot:
ggplot(plot_data, aes(x = flow)) + 
  geom_point(aes(y = .pred)) + 
  geom_errorbar(aes(ymin = .pred_lower, 
                    ymax = .pred_upper),
                width = .2) + 
  labs(y = "ave v")

# set the prior distribution
prior_dist <- rstanarm::student_t(df = 1)

set.seed(123)

# make the parsnip model
bayes_mod <-   
  linear_reg() %>% 
  set_engine("stan", 
             prior_intercept = prior_dist, 
             prior = prior_dist) 

# train the model
bayes_fit <- 
  bayes_mod %>% 
  fit(ave.v ~ flow * chl * guano * light, data = df)

print(bayes_fit, digits = 5)

tidy(bayes_fit, conf.int = TRUE)

bayes_plot_data <- 
  new_points %>% 
  bind_cols(predict(bayes_fit, new_data = new_points)) %>% 
  bind_cols(predict(bayes_fit, new_data = new_points, type = "conf_int"))

ggplot(bayes_plot_data, aes(x = flow)) + 
  geom_point(aes(y = .pred)) + 
  geom_errorbar(aes(ymin = .pred_lower, ymax = .pred_upper), width = .2) + 
  labs(y = "ave.v") + 
  ggtitle("Bayesian model with t(1) prior distribution")

### Tuning the model in sections

df %>% 
  group_by(light) %>% 
  summarize(med_flow = median(flow))

bayes_mod %>% 
  fit(ave.v ~ flow * chl * guano * light, data = df)

ggplot(df,
       aes(flow, ave.v)) +      # returns a ggplot object 
  geom_jitter() +                         # same
  geom_smooth(method = lm, se = FALSE) +  # same                    
  labs(x = "flow", y = "ave.v")         # etc

Inputs


rf_mod <- ## creates random forest model
  rand_forest(trees = 1000) %>% 
  set_engine("ranger") %>% 
  set_mode("regression")


set.seed(234)  ## comment out to get random runs

rf_fit <- ## fits random forest model to whole dataset
  rf_mod %>% 
  fit(ave.v ~ flow * chl * guano * light, data = df)
rf_fit

rf_pred <- predict(rf_fit, df)  
plot(df$ave.v, rf_pred$.pred, main = "corr - 0.7294325")  ## observed vs predicted
cor <- cor.test(df$ave.v, rf_pred$.pred)  ## gives correlation coef

rf_split <- initial_split(df %>% select(flow, chl, guano, light, ave.v), ## splits cases base on initial results
                            strata = NULL)  ## with strata = NULL splits 11/31, rf_test has all guano absent, 50/50 light split, uneven flow split (0, 3, 3, 5.9, 5.9, 5.9, 8.9 x5), and random chl values.
                                            ## with strata = flow, splits 12/30, rf_test has 1 guano present, 50/50 light split, even flow splits (3x 0, 3, 4x 5.9 and 2x 8.9), and more even chl values.
## play with prop = 0.8, 0.9, etc


rf_train <- training(rf_split)  ##creates training and testing datasets
rf_test  <- testing(rf_split)

## comparisons to test data using ROC and accuracy to measure performance

rf_fit2 <- ## fits random forest model to training dataset
  rf_mod %>% 
  fit(ave.v ~ flow * chl * guano * light, data = rf_train)
rf_fit2

rf_pred2 <- predict(rf_fit2, rf_test) ## compares to test data
plot(rf_test$ave.v, rf_pred2$.pred, main = "corr - 0.2700745")  ## observed vs predicted, can add cor value from cor.test below
cor2 <- cor.test(rf_test$ave.v, rf_pred2$.pred)  ## gives correlation coef
cor2$estimate
### can run with different seeds and splits, strata = NULL
#### loop and run 10 times with diff seeds
## look at consistency of results

Models

##### Example models
library(tidymodels)  # for the parsnip package, along with the rest of tidymodels

# Helper packages
library(readr)       # for importing data
library(broom.mixed) # for converting bayesian models to tidy tibbles
library(dotwhisker)  # for visualizing regression results
library(vip)         # for variable importance plots


rm(list=ls(all=TRUE))   ## removes the previous workspace and environment so that we only have the data we need loaded in the session
load("~/Bigelow/Data/ParameterizationModel.15.07.24.Rdata")

seeing if this fixes error

dim(parameters)
glimpse(parameters)

dim(conditions)
glimpse(conditions)

df <- data.frame(conditions[,1],conditions[,2],conditions[,3],conditions[,4],parameters[,12])
colnames(df) <- c("flow","chl", "guano", "light",colnames(parameters)[12])
df
df<-na.omit(df)

source("notebook16-functions.R")
## fix to pull col name or number into model??
colnames(df) <- c("flow","chl", "guano", "light", colnames(parameters)[12])
c.v <- NULL
c.v_train <- NULL
p <- NULL
rsq2 <- NULL
mod1 <- NULL
mod2 <- NULL
mod3 <- NULL
node.purity <- data.frame(matrix (NA, nrow = 10, ncol = 5))
colnames(node.purity) <- c( "c.e", "c.e.t", "r.squared", "p-value", "node purity")


library(ggplot2)
library(GGally)
library(hexbin)
library(diptest)
library(randomForest)
library(reprtree)

## loop through different swimming parameters contain this loop within the bigger loop
colnames(parameters)


for (j in colnames(parameters)){
    df <- data.frame(conditions[,1],conditions[,2],conditions[,3],conditions[,4], parameters[,j])
    colnames(df) <- c("flow","chl", "guano", "light", j)
    df
    df<-na.omit(df)
    source("notebook16-functions.R")
    print(colnames(df))

      for (i in 1:10){  

        c.e <- rf.skill(df = df, trees = 2000, col1 = colnames(df)[5]) 
        c.v <- rbind(c.e, c.v)  
        c.e.t <- rf.skill.test(df = df, trees = 2000, col1 = colnames(df)[5], prop = 0.8, strata = NULL, do.plot = FALSE)   ## training data
        c.v_train <- rbind(c.e.t, c.v_train)
        rsq1 <- rf.fit(df = df, trees = 2000, col1 = colnames(df)[5])
        rsq2 <- rbind(rsq1, rsq2)
  
        output.test <- model.test(df = df, trees = 2000, col1 = colnames(df)[5], prop = 0.8, strata = NULL, do.plot = TRUE)
        
        ## output node purity
  
        ## create function to filter edge effect out (.5 cm out, dist to edge)
  
        ##ggsave(filename=paste('~/Bigelow/Figures/tidymodels Output/ave v/', i, '.tiff', sep = ''), plot = p,     width = 24 , height = 12)
      }
    mod1 <- rbind(mod1, c.v)  ## add identifier for each parameter into data frame
    mod2 <- rbind(mod2, c.v_train)
    mod3 <- rbind(mod3, rsq2)
    
    ## input average of p value, ce, cv, node purity and rsq into each column and row in matrix for each parameter and model type
    
    ## rank on best fit
    ## table for report of stats on models
    
}

## take best model for each pararmeter into simulation
## try vel mean and sd, and turn mean and sd 

rf_pred_train <- predict(rf_fit_train, rf_test) ## need to predict each parameter within simulation

## environmental input to swimming, eg. node purity



c.v <- as.data.frame(c.v)
c.v_train <- as.data.frame(c.v_train)
rsq2 <- as.data.frame(rsq2)

node.purity$c.e <- c.v$V1
node.purity$c.e.t <- c.v_train$cor
node.purity$r.squared <- rsq2$V1

Node Purity heat map

  ### to save each plot as an individual graph
  #jpeg(filename= paste('~/Bigelow/Figures/tidymodels Output/fit', i,'.jpeg', sep = ''), width = 960,      height = 780)
  #plot(df$response, rf_pred$.pred, main = paste("corr = ", cor$estimate))  ## observed vs predicted
  #dev.off()


  #best_tree <- rf.skill.test(df = df, trees = 2000, "ave.v", prop = 0.5, strata = NULL, do.plot = TRUE) ## not working....
  #bt <- rbind(best_tree, bt)

## look at things outside random forest


rf_training_pred <- 
  predict(rf_fit, rf_train) %>% 
  bind_cols(predict(rf_fit, rf_train, type = "numeric")) %>% 
  # Add the true outcome data back in
  bind_cols(rf_train %>% 
              select(flow, chl, guano, light))

rf_training_pred$light <- as.factor(rf_training_pred$light)
rf_training_pred$guano <- as.factor(rf_training_pred$guano)

rf_training_pred %>%                # training set predictions
  roc_auc(truth = light, .pred...1)

rf_training_pred %>%                # training set predictions, only works for factors
  accuracy(truth = ave.v, .pred...2)

## now that the model has exceptional performance lets move to the test dataset

rf_testing_pred <- 
  predict(rf_fit, rf_test) %>% 
  bind_cols(predict(rf_fit, rf_test, type = "numeric")) %>% 
  bind_cols(rf_test %>% select(flow, chl, guano, light))

rf_testing_pred$light <- as.factor(rf_testing_pred$light)
rf_testing_pred$guano <- as.factor(rf_testing_pred$guano)

rf_testing_pred %>%                   # test set predictions
  roc_auc(truth = light, .pred...1)

rf_testing_pred %>%                   # test set predictions
  accuracy(truth = light, guano)

## differences caused by training set error (bias) by model

###### resampling to the rescue

set.seed(345)
folds <- vfold_cv(rf_train, v = 10)
folds

rf_wf <- ## bundles workflow and random forest model together without a recipe needed
  workflow() %>%
  add_model(rf_mod) %>%
  add_formula(ave.v ~ .)

set.seed(456)
rf_fit_rs <- 
  rf_wf %>% 
  fit_resamples(folds)  ##fits resamples

rf_fit_rs  ## .metrics column contains metrics on model performance

collect_metrics(rf_fit_rs)  ##manually unnests meterics data

rf_testing_pred %>%                   # test set predictions (AS ABOVE)
  roc_auc(truth = light, .pred...1)

rf_testing_pred %>%                   # test set predictions  (AS ABOVE)
  accuracy(truth = light, guano)

Tuning the model

library(glmnet)
library(rpart.plot)  # for visualizing a decision tree
library(vip)         # for variable importance plots

tune_spec <- 
  decision_tree(  ## this is the type of model
    cost_complexity = tune(),
    tree_depth = tune()
  ) %>% 
  set_engine("rpart") %>% 
  set_mode("regression")

tune_spec

tree_grid <- grid_regular(cost_complexity(),
                          tree_depth(),
                          levels = 5)

tree_grid

tree_grid %>% ## shows each level we will tune the model at
  count(tree_depth)

set.seed(234) ## don't understand what these do??
rf_folds <- vfold_cv(rf_train)  ## creates cross-validation folds for tuning

set.seed(345)

tree_wf <- workflow() %>%  ##creates the workflow
  add_model(tune_spec) %>%
  add_formula(ave.v ~ .)

tree_res <- ## resamples and tunes model
  tree_wf %>% 
  tune_grid(
    resamples = rf_folds,
    grid = tree_grid
    )

tree_res  ## gives tuning results

tree_res %>% 
  collect_metrics()  ## collects metrics from tuned models


tree_res %>%
  collect_metrics() %>%
  mutate(tree_depth = factor(tree_depth)) %>%
  ggplot(aes(cost_complexity, mean, color = tree_depth)) +
  geom_line(size = 1.5, alpha = 0.6) +
  geom_point(size = 2) +
  facet_wrap(~ .metric, scales = "free", nrow = 2) +
  scale_x_log10(labels = scales::label_number()) +
  scale_color_viridis_d(option = "plasma", begin = .9, end = 0)

## stubbiest tree with a depth of 1 performed the worst
## deepest tree with depth of 15 did better

tree_res %>%
  show_best(metric = "rmse")  ## shows best model fit

best_tree <- tree_res %>%  ## pulls out data on the best fit
  select_best(metric = "rmse")

best_tree ## summary of best tree model

final_wf <- ## create workflow from best tree model after tuning
  tree_wf %>% 
  finalize_workflow(best_tree)

final_wf

final_fit <- ## create final model from new fit
  final_wf %>%
  last_fit(rf_split) 

final_fit %>%
  collect_metrics()


final_fit %>%  ## plot ROC and compare performance after tuning
  collect_predictions() %>% 
  roc_curve(flow, ave.v) %>% ### NOT WORKING
  autoplot()

final_tree <- extract_workflow(final_fit)  ## extract our final fit for future use
final_tree

final_tree %>%  ## creates workflow plot
  extract_fit_engine() %>%
  rpart.plot(roundint = FALSE)

final_tree %>% ## shows which variables are most important to the model in a plot
  extract_fit_parsnip() %>% 
  vip()

args(decision_tree)

Bigger RF Model

cores <- parallel::detectCores() ## sees how many cores we have to process the data
cores

rf_mod <- ## random forest model generation, parallel processing of models
  rand_forest(mtry = tune(), min_n = tune(), trees = 1000) %>% 
  set_engine("ranger", num.threads = cores) %>% 
  set_mode("regression")

rf_recipe <- ## create random forest model recipe
  recipe(ave.v ~ ., data = df) 
  
rf_workflow <- ## create random forest model workflow
  workflow() %>% 
  add_model(rf_mod) %>% 
  add_recipe(rf_recipe)

rf_mod

val_set <- validation_split(df, 
                            strata = flow, 
                            prop = 0.80)
val_set
# show what will be tuned
extract_parameter_set_dials(rf_mod)

set.seed(345)
rf_res <- 
  rf_workflow %>% 
  tune_grid(val_set,
            grid = 25,
            control = control_grid(save_pred = TRUE),
            metrics = metric_set(rmse))

rf_res %>% ## shows 5 best random forest models out of the 25 candidates
  show_best(metric = "rmse")

autoplot(rf_res)  ## plot results
rf_best <- ## creates model with best predictors
  rf_res %>% 
  select_best(metric = "rmse")
rf_best

rf_res %>% ## collects data for ROC curve plot
  collect_predictions()

rf_best$mtry <- as.integer(rf_best$mtry)


##NOT WORKING
rf_auc <- ## creates set of models with best model and model model for comparison
  rf_res %>% 
  collect_predictions(parameters = rf_best) %>% 
  roc_curve(mtry, .pred) %>% 
  mutate(model = "Random Forest")  

##NOT WORKING
bind_rows(rf_best, rf_res) %>% ## plots model comparisons on ROC curve
  ggplot(aes(x = 1 - specificity, y = sensitivity, col = model)) + 
  geom_path(lwd = 1.5, alpha = 0.8) +
  geom_abline(lty = 3) + 
  coord_equal() + 
  scale_color_viridis_d(option = "plasma", end = .6)
################ last model after tuning 

# the last model
last_rf_mod <- 
  rand_forest(mtry = 8, min_n = 7, trees = 1000) %>% 
  set_engine("ranger", num.threads = cores, importance = "impurity") %>% 
  set_mode("regression")

# the last workflow
last_rf_workflow <- 
  rf_workflow %>% 
  update_model(last_rf_mod)

# the last fit
set.seed(345)
last_rf_fit <- 
  last_rf_workflow %>% 
  last_fit(rf_split)

last_rf_fit

last_rf_fit %>%  ## collect metrics from final model
  collect_metrics()

last_rf_fit %>% ## updates model fit
  extract_fit_parsnip() %>% 
  vip(num_features = 20)

##NOT WORKING
last_rf_fit %>% ## plots best ROC curve, with best set of hyperparameters as predictors
  collect_predictions() %>% 
  roc_curve(ave.v, .pred...1) %>% 
  autoplot()
LS0tDQp0aXRsZTogIm5vdGVib29rMTYtdGlkeW1vZGVscyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQpBdHRlbXB0aW5nIHRvIHVzZSB0aWR5bW9kZWxzIGZvciBwcm9jZXNzaW5nIG91ciBJQk0ga3JpbGwgZGF0YSBhbmQgc2VsZWN0aW5nIHZhcmlhYmxlcyBvZiBpbXBvcnRhbmNlIGZvciBlYWNoIHN3aW1taW5nIHBhcmFtZXRlcg0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeW1vZGVscykgICMgZm9yIHRoZSBwYXJzbmlwIHBhY2thZ2UsIGFsb25nIHdpdGggdGhlIHJlc3Qgb2YgdGlkeW1vZGVscw0KDQojIEhlbHBlciBwYWNrYWdlcw0KbGlicmFyeShyZWFkcikgICAgICAgIyBmb3IgaW1wb3J0aW5nIGRhdGENCmxpYnJhcnkoYnJvb20ubWl4ZWQpICMgZm9yIGNvbnZlcnRpbmcgYmF5ZXNpYW4gbW9kZWxzIHRvIHRpZHkgdGliYmxlcw0KbGlicmFyeShkb3R3aGlza2VyKSAgIyBmb3IgdmlzdWFsaXppbmcgcmVncmVzc2lvbiByZXN1bHRzDQpsaWJyYXJ5KHZpcCkgICAgICAgICAjIGZvciB2YXJpYWJsZSBpbXBvcnRhbmNlIHBsb3RzDQoNCg0Kcm0obGlzdD1scyhhbGw9VFJVRSkpICAgIyMgcmVtb3ZlcyB0aGUgcHJldmlvdXMgd29ya3NwYWNlIGFuZCBlbnZpcm9ubWVudCBzbyB0aGF0IHdlIG9ubHkgaGF2ZSB0aGUgZGF0YSB3ZSBuZWVkIGxvYWRlZCBpbiB0aGUgc2Vzc2lvbg0KbG9hZCgifi9Qb3N0LWRvYy9rcmlsbC10YW5rLWNvZGUvRGF0YVByb2Nlc3NpbmcvTm90ZWJvb2tzL1BhcmFtZXRlcml6YXRpb25Nb2RlbC5SZGF0YSIpDQoNCg0KZGltKHBhcmFtZXRlcnMpDQpnbGltcHNlKHBhcmFtZXRlcnMpDQoNCmRpbShjb25kaXRpb25zKQ0KZ2xpbXBzZShjb25kaXRpb25zKQ0KDQpkZiA8LSBkYXRhLmZyYW1lKGNvbmRpdGlvbnNbLDFdLGNvbmRpdGlvbnNbLDJdLGNvbmRpdGlvbnNbLDNdLGNvbmRpdGlvbnNbLDRdLHBhcmFtZXRlcnNbLDFdLCBwYXJhbWV0ZXJzWyw3XSkNCmNvbG5hbWVzKGRmKSA8LSBjKCJmbG93IiwiY2hsIiwgImd1YW5vIiwgImxpZ2h0IiwiYXZlLnYiLCAiZGlwLnRlc3QiKQ0KZGYNCmRmPC1uYS5vbWl0KGRmKQ0KDQoNCmF2ZS52IH4gZmxvdyAqIGNobCAqIGd1YW5vICogbGlnaHQNCg0KDQpsaW5lYXJfcmVnKCkNCg0KbGluZWFyX3JlZygpICU+JSANCiAgc2V0X2VuZ2luZSgia2VyYXMiKQ0KDQpsbV9tb2QgPC0gbGluZWFyX3JlZygpDQoNCmxtX2ZpdCA8LSANCiAgbG1fbW9kICU+JSANCiAgZml0KGF2ZS52IH4gZmxvdyAqIGNobCAqIGd1YW5vICogbGlnaHQsIGRhdGEgPSBkZikgICMjIGNyZWF0ZXMgYSBsaW5lYXIgbW9kZWwgKExNKQ0KbG1fZml0DQoNCnRpZHkobG1fZml0KSAgIyMgc3VtbWFyeSBvZiBtb2RlbA0KDQoNCnRpZHkobG1fZml0KSAlPiUgDQogIGR3cGxvdChkb3RfYXJncyA9IGxpc3Qoc2l6ZSA9IDIsIGNvbG9yID0gImJsYWNrIiksDQogICAgICAgICB3aGlza2VyX2FyZ3MgPSBsaXN0KGNvbG9yID0gImJsYWNrIiksDQogICAgICAgICB2bGluZSA9IGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIGNvbG91ciA9ICJncmV5NTAiLCBsaW5ldHlwZSA9IDIpKQ0KDQpuZXdfcG9pbnRzIDwtIGV4cGFuZC5ncmlkKGZsb3cgPSA2LjEsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGNobCA9IDE5LA0KICAgICAgICAgICAgICAgICAgICAgICAgICBndWFubyA9IDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGxpZ2h0ID0gMCkgIyMgY3JlYXRlIG5ldyBwb2ludHMNCm5ld19wb2ludHMNCg0KbWVhbl9wcmVkIDwtIHByZWRpY3QobG1fZml0LCBuZXdfZGF0YSA9IG5ld19wb2ludHMpICAjIyBtZWFuIHByZWRpY3RlZCBib2R5IHdpZHRoDQptZWFuX3ByZWQNCg0KY29uZl9pbnRfcHJlZCA8LSBwcmVkaWN0KGxtX2ZpdCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgbmV3X2RhdGEgPSBuZXdfcG9pbnRzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gImNvbmZfaW50IikgICMjIGNvbmZpZGVuY2UgaW50ZXJ2YWwgcHJlZGljdGlvbg0KY29uZl9pbnRfcHJlZA0KDQpwbG90X2RhdGEgPC0gDQogIG5ld19wb2ludHMgJT4lIA0KICBiaW5kX2NvbHMobWVhbl9wcmVkKSAlPiUgDQogIGJpbmRfY29scyhjb25mX2ludF9wcmVkKQ0KDQojIGFuZCBwbG90Og0KZ2dwbG90KHBsb3RfZGF0YSwgYWVzKHggPSBmbG93KSkgKyANCiAgZ2VvbV9wb2ludChhZXMoeSA9IC5wcmVkKSkgKyANCiAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbiA9IC5wcmVkX2xvd2VyLCANCiAgICAgICAgICAgICAgICAgICAgeW1heCA9IC5wcmVkX3VwcGVyKSwNCiAgICAgICAgICAgICAgICB3aWR0aCA9IC4yKSArIA0KICBsYWJzKHkgPSAiYXZlIHYiKQ0KDQojIHNldCB0aGUgcHJpb3IgZGlzdHJpYnV0aW9uDQpwcmlvcl9kaXN0IDwtIHJzdGFuYXJtOjpzdHVkZW50X3QoZGYgPSAxKQ0KDQpzZXQuc2VlZCgxMjMpDQoNCiMgbWFrZSB0aGUgcGFyc25pcCBtb2RlbA0KYmF5ZXNfbW9kIDwtICAgDQogIGxpbmVhcl9yZWcoKSAlPiUgDQogIHNldF9lbmdpbmUoInN0YW4iLCANCiAgICAgICAgICAgICBwcmlvcl9pbnRlcmNlcHQgPSBwcmlvcl9kaXN0LCANCiAgICAgICAgICAgICBwcmlvciA9IHByaW9yX2Rpc3QpIA0KDQojIHRyYWluIHRoZSBtb2RlbA0KYmF5ZXNfZml0IDwtIA0KICBiYXllc19tb2QgJT4lIA0KICBmaXQoYXZlLnYgfiBmbG93ICogY2hsICogZ3Vhbm8gKiBsaWdodCwgZGF0YSA9IGRmKQ0KDQpwcmludChiYXllc19maXQsIGRpZ2l0cyA9IDUpDQoNCnRpZHkoYmF5ZXNfZml0LCBjb25mLmludCA9IFRSVUUpDQoNCmJheWVzX3Bsb3RfZGF0YSA8LSANCiAgbmV3X3BvaW50cyAlPiUgDQogIGJpbmRfY29scyhwcmVkaWN0KGJheWVzX2ZpdCwgbmV3X2RhdGEgPSBuZXdfcG9pbnRzKSkgJT4lIA0KICBiaW5kX2NvbHMocHJlZGljdChiYXllc19maXQsIG5ld19kYXRhID0gbmV3X3BvaW50cywgdHlwZSA9ICJjb25mX2ludCIpKQ0KDQpnZ3Bsb3QoYmF5ZXNfcGxvdF9kYXRhLCBhZXMoeCA9IGZsb3cpKSArIA0KICBnZW9tX3BvaW50KGFlcyh5ID0gLnByZWQpKSArIA0KICBnZW9tX2Vycm9yYmFyKGFlcyh5bWluID0gLnByZWRfbG93ZXIsIHltYXggPSAucHJlZF91cHBlciksIHdpZHRoID0gLjIpICsgDQogIGxhYnMoeSA9ICJhdmUudiIpICsgDQogIGdndGl0bGUoIkJheWVzaWFuIG1vZGVsIHdpdGggdCgxKSBwcmlvciBkaXN0cmlidXRpb24iKQ0KDQojIyMgVHVuaW5nIHRoZSBtb2RlbCBpbiBzZWN0aW9ucw0KDQpkZiAlPiUgDQogIGdyb3VwX2J5KGxpZ2h0KSAlPiUgDQogIHN1bW1hcml6ZShtZWRfZmxvdyA9IG1lZGlhbihmbG93KSkNCg0KYmF5ZXNfbW9kICU+JSANCiAgZml0KGF2ZS52IH4gZmxvdyAqIGNobCAqIGd1YW5vICogbGlnaHQsIGRhdGEgPSBkZikNCg0KZ2dwbG90KGRmLA0KICAgICAgIGFlcyhmbG93LCBhdmUudikpICsgICAgICAjIHJldHVybnMgYSBnZ3Bsb3Qgb2JqZWN0IA0KICBnZW9tX2ppdHRlcigpICsgICAgICAgICAgICAgICAgICAgICAgICAgIyBzYW1lDQogIGdlb21fc21vb3RoKG1ldGhvZCA9IGxtLCBzZSA9IEZBTFNFKSArICAjIHNhbWUgICAgICAgICAgICAgICAgICAgIA0KICBsYWJzKHggPSAiZmxvdyIsIHkgPSAiYXZlLnYiKSAgICAgICAgICMgZXRjDQpgYGANCklucHV0cw0KDQpgYGB7cn0NCg0KcmZfbW9kIDwtICMjIGNyZWF0ZXMgcmFuZG9tIGZvcmVzdCBtb2RlbA0KICByYW5kX2ZvcmVzdCh0cmVlcyA9IDEwMDApICU+JSANCiAgc2V0X2VuZ2luZSgicmFuZ2VyIikgJT4lIA0KICBzZXRfbW9kZSgicmVncmVzc2lvbiIpDQoNCg0Kc2V0LnNlZWQoMjM0KSAgIyMgY29tbWVudCBvdXQgdG8gZ2V0IHJhbmRvbSBydW5zDQoNCnJmX2ZpdCA8LSAjIyBmaXRzIHJhbmRvbSBmb3Jlc3QgbW9kZWwgdG8gd2hvbGUgZGF0YXNldA0KICByZl9tb2QgJT4lIA0KICBmaXQoYXZlLnYgfiBmbG93ICogY2hsICogZ3Vhbm8gKiBsaWdodCwgZGF0YSA9IGRmKQ0KcmZfZml0DQoNCnJmX3ByZWQgPC0gcHJlZGljdChyZl9maXQsIGRmKSAgDQpwbG90KGRmJGF2ZS52LCByZl9wcmVkJC5wcmVkLCBtYWluID0gImNvcnIgLSAwLjcyOTQzMjUiKSAgIyMgb2JzZXJ2ZWQgdnMgcHJlZGljdGVkDQpjb3IgPC0gY29yLnRlc3QoZGYkYXZlLnYsIHJmX3ByZWQkLnByZWQpICAjIyBnaXZlcyBjb3JyZWxhdGlvbiBjb2VmDQoNCnJmX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQoZGYgJT4lIHNlbGVjdChmbG93LCBjaGwsIGd1YW5vLCBsaWdodCwgYXZlLnYpLCAjIyBzcGxpdHMgY2FzZXMgYmFzZSBvbiBpbml0aWFsIHJlc3VsdHMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJhdGEgPSBOVUxMKSAgIyMgd2l0aCBzdHJhdGEgPSBOVUxMIHNwbGl0cyAxMS8zMSwgcmZfdGVzdCBoYXMgYWxsIGd1YW5vIGFic2VudCwgNTAvNTAgbGlnaHQgc3BsaXQsIHVuZXZlbiBmbG93IHNwbGl0ICgwLCAzLCAzLCA1LjksIDUuOSwgNS45LCA4LjkgeDUpLCBhbmQgcmFuZG9tIGNobCB2YWx1ZXMuDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMjIHdpdGggc3RyYXRhID0gZmxvdywgc3BsaXRzIDEyLzMwLCByZl90ZXN0IGhhcyAxIGd1YW5vIHByZXNlbnQsIDUwLzUwIGxpZ2h0IHNwbGl0LCBldmVuIGZsb3cgc3BsaXRzICgzeCAwLCAzLCA0eCA1LjkgYW5kIDJ4IDguOSksIGFuZCBtb3JlIGV2ZW4gY2hsIHZhbHVlcy4NCiMjIHBsYXkgd2l0aCBwcm9wID0gMC44LCAwLjksIGV0Yw0KDQoNCnJmX3RyYWluIDwtIHRyYWluaW5nKHJmX3NwbGl0KSAgIyNjcmVhdGVzIHRyYWluaW5nIGFuZCB0ZXN0aW5nIGRhdGFzZXRzDQpyZl90ZXN0ICA8LSB0ZXN0aW5nKHJmX3NwbGl0KQ0KDQojIyBjb21wYXJpc29ucyB0byB0ZXN0IGRhdGEgdXNpbmcgUk9DIGFuZCBhY2N1cmFjeSB0byBtZWFzdXJlIHBlcmZvcm1hbmNlDQoNCnJmX2ZpdDIgPC0gIyMgZml0cyByYW5kb20gZm9yZXN0IG1vZGVsIHRvIHRyYWluaW5nIGRhdGFzZXQNCiAgcmZfbW9kICU+JSANCiAgZml0KGF2ZS52IH4gZmxvdyAqIGNobCAqIGd1YW5vICogbGlnaHQsIGRhdGEgPSByZl90cmFpbikNCnJmX2ZpdDINCg0KcmZfcHJlZDIgPC0gcHJlZGljdChyZl9maXQyLCByZl90ZXN0KSAjIyBjb21wYXJlcyB0byB0ZXN0IGRhdGENCnBsb3QocmZfdGVzdCRhdmUudiwgcmZfcHJlZDIkLnByZWQsIG1haW4gPSAiY29yciAtIDAuMjcwMDc0NSIpICAjIyBvYnNlcnZlZCB2cyBwcmVkaWN0ZWQsIGNhbiBhZGQgY29yIHZhbHVlIGZyb20gY29yLnRlc3QgYmVsb3cNCmNvcjIgPC0gY29yLnRlc3QocmZfdGVzdCRhdmUudiwgcmZfcHJlZDIkLnByZWQpICAjIyBnaXZlcyBjb3JyZWxhdGlvbiBjb2VmDQpjb3IyJGVzdGltYXRlDQojIyMgY2FuIHJ1biB3aXRoIGRpZmZlcmVudCBzZWVkcyBhbmQgc3BsaXRzLCBzdHJhdGEgPSBOVUxMDQojIyMjIGxvb3AgYW5kIHJ1biAxMCB0aW1lcyB3aXRoIGRpZmYgc2VlZHMNCiMjIGxvb2sgYXQgY29uc2lzdGVuY3kgb2YgcmVzdWx0cw0KYGBgDQoNCk1vZGVscw0KYGBge3J9DQojIyMjIyBFeGFtcGxlIG1vZGVscw0KbGlicmFyeSh0aWR5bW9kZWxzKSAgIyBmb3IgdGhlIHBhcnNuaXAgcGFja2FnZSwgYWxvbmcgd2l0aCB0aGUgcmVzdCBvZiB0aWR5bW9kZWxzDQoNCiMgSGVscGVyIHBhY2thZ2VzDQpsaWJyYXJ5KHJlYWRyKSAgICAgICAjIGZvciBpbXBvcnRpbmcgZGF0YQ0KbGlicmFyeShicm9vbS5taXhlZCkgIyBmb3IgY29udmVydGluZyBiYXllc2lhbiBtb2RlbHMgdG8gdGlkeSB0aWJibGVzDQpsaWJyYXJ5KGRvdHdoaXNrZXIpICAjIGZvciB2aXN1YWxpemluZyByZWdyZXNzaW9uIHJlc3VsdHMNCmxpYnJhcnkodmlwKSAgICAgICAgICMgZm9yIHZhcmlhYmxlIGltcG9ydGFuY2UgcGxvdHMNCg0KDQpybShsaXN0PWxzKGFsbD1UUlVFKSkgICAjIyByZW1vdmVzIHRoZSBwcmV2aW91cyB3b3Jrc3BhY2UgYW5kIGVudmlyb25tZW50IHNvIHRoYXQgd2Ugb25seSBoYXZlIHRoZSBkYXRhIHdlIG5lZWQgbG9hZGVkIGluIHRoZSBzZXNzaW9uDQpsb2FkKCJ+L0JpZ2Vsb3cvRGF0YS9QYXJhbWV0ZXJpemF0aW9uTW9kZWwuMTUuMDcuMjQuUmRhdGEiKQ0KYGBgDQoNCnNlZWluZyBpZiB0aGlzIGZpeGVzIGVycm9yDQpgYGB7cn0NCmRpbShwYXJhbWV0ZXJzKQ0KZ2xpbXBzZShwYXJhbWV0ZXJzKQ0KDQpkaW0oY29uZGl0aW9ucykNCmdsaW1wc2UoY29uZGl0aW9ucykNCg0KZGYgPC0gZGF0YS5mcmFtZShjb25kaXRpb25zWywxXSxjb25kaXRpb25zWywyXSxjb25kaXRpb25zWywzXSxjb25kaXRpb25zWyw0XSxwYXJhbWV0ZXJzWywxMl0pDQpjb2xuYW1lcyhkZikgPC0gYygiZmxvdyIsImNobCIsICJndWFubyIsICJsaWdodCIsY29sbmFtZXMocGFyYW1ldGVycylbMTJdKQ0KZGYNCmRmPC1uYS5vbWl0KGRmKQ0KDQpzb3VyY2UoIm5vdGVib29rMTYtZnVuY3Rpb25zLlIiKQ0KIyMgZml4IHRvIHB1bGwgY29sIG5hbWUgb3IgbnVtYmVyIGludG8gbW9kZWw/Pw0KY29sbmFtZXMoZGYpIDwtIGMoImZsb3ciLCJjaGwiLCAiZ3Vhbm8iLCAibGlnaHQiLCBjb2xuYW1lcyhwYXJhbWV0ZXJzKVsxMl0pDQpjLnYgPC0gTlVMTA0KYy52X3RyYWluIDwtIE5VTEwNCnAgPC0gTlVMTA0KcnNxMiA8LSBOVUxMDQptb2QxIDwtIE5VTEwNCm1vZDIgPC0gTlVMTA0KbW9kMyA8LSBOVUxMDQpub2RlLnB1cml0eSA8LSBkYXRhLmZyYW1lKG1hdHJpeCAoTkEsIG5yb3cgPSAxMCwgbmNvbCA9IDUpKQ0KY29sbmFtZXMobm9kZS5wdXJpdHkpIDwtIGMoICJjLmUiLCAiYy5lLnQiLCAici5zcXVhcmVkIiwgInAtdmFsdWUiLCAibm9kZSBwdXJpdHkiKQ0KDQoNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoR0dhbGx5KQ0KbGlicmFyeShoZXhiaW4pDQpsaWJyYXJ5KGRpcHRlc3QpDQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNCmxpYnJhcnkocmVwcnRyZWUpDQoNCiMjIGxvb3AgdGhyb3VnaCBkaWZmZXJlbnQgc3dpbW1pbmcgcGFyYW1ldGVycyBjb250YWluIHRoaXMgbG9vcCB3aXRoaW4gdGhlIGJpZ2dlciBsb29wDQpjb2xuYW1lcyhwYXJhbWV0ZXJzKQ0KDQoNCmZvciAoaiBpbiBjb2xuYW1lcyhwYXJhbWV0ZXJzKSl7DQogICAgZGYgPC0gZGF0YS5mcmFtZShjb25kaXRpb25zWywxXSxjb25kaXRpb25zWywyXSxjb25kaXRpb25zWywzXSxjb25kaXRpb25zWyw0XSwgcGFyYW1ldGVyc1ssal0pDQogICAgY29sbmFtZXMoZGYpIDwtIGMoImZsb3ciLCJjaGwiLCAiZ3Vhbm8iLCAibGlnaHQiLCBqKQ0KICAgIGRmDQogICAgZGY8LW5hLm9taXQoZGYpDQogICAgc291cmNlKCJub3RlYm9vazE2LWZ1bmN0aW9ucy5SIikNCiAgICBwcmludChjb2xuYW1lcyhkZikpDQoNCiAgICAgIGZvciAoaSBpbiAxOjEwKXsgIA0KDQogICAgICAgIGMuZSA8LSByZi5za2lsbChkZiA9IGRmLCB0cmVlcyA9IDIwMDAsIGNvbDEgPSBjb2xuYW1lcyhkZilbNV0pIA0KICAgICAgICBjLnYgPC0gcmJpbmQoYy5lLCBjLnYpICANCiAgICAgICAgYy5lLnQgPC0gcmYuc2tpbGwudGVzdChkZiA9IGRmLCB0cmVlcyA9IDIwMDAsIGNvbDEgPSBjb2xuYW1lcyhkZilbNV0sIHByb3AgPSAwLjgsIHN0cmF0YSA9IE5VTEwsIGRvLnBsb3QgPSBGQUxTRSkgICAjIyB0cmFpbmluZyBkYXRhDQogICAgICAgIGMudl90cmFpbiA8LSByYmluZChjLmUudCwgYy52X3RyYWluKQ0KICAgICAgICByc3ExIDwtIHJmLmZpdChkZiA9IGRmLCB0cmVlcyA9IDIwMDAsIGNvbDEgPSBjb2xuYW1lcyhkZilbNV0pDQogICAgICAgIHJzcTIgPC0gcmJpbmQocnNxMSwgcnNxMikNCiAgDQogICAgICAgIG91dHB1dC50ZXN0IDwtIG1vZGVsLnRlc3QoZGYgPSBkZiwgdHJlZXMgPSAyMDAwLCBjb2wxID0gY29sbmFtZXMoZGYpWzVdLCBwcm9wID0gMC44LCBzdHJhdGEgPSBOVUxMLCBkby5wbG90ID0gVFJVRSkNCiAgICAgICAgDQogICAgICAgICMjIG91dHB1dCBub2RlIHB1cml0eQ0KICANCiAgICAgICAgIyMgY3JlYXRlIGZ1bmN0aW9uIHRvIGZpbHRlciBlZGdlIGVmZmVjdCBvdXQgKC41IGNtIG91dCwgZGlzdCB0byBlZGdlKQ0KICANCiAgICAgICAgIyNnZ3NhdmUoZmlsZW5hbWU9cGFzdGUoJ34vQmlnZWxvdy9GaWd1cmVzL3RpZHltb2RlbHMgT3V0cHV0L2F2ZSB2LycsIGksICcudGlmZicsIHNlcCA9ICcnKSwgcGxvdCA9IHAsICAgICB3aWR0aCA9IDI0ICwgaGVpZ2h0ID0gMTIpDQogICAgICB9DQogICAgbW9kMSA8LSByYmluZChtb2QxLCBjLnYpICAjIyBhZGQgaWRlbnRpZmllciBmb3IgZWFjaCBwYXJhbWV0ZXIgaW50byBkYXRhIGZyYW1lDQogICAgbW9kMiA8LSByYmluZChtb2QyLCBjLnZfdHJhaW4pDQogICAgbW9kMyA8LSByYmluZChtb2QzLCByc3EyKQ0KICAgIA0KICAgICMjIGlucHV0IGF2ZXJhZ2Ugb2YgcCB2YWx1ZSwgY2UsIGN2LCBub2RlIHB1cml0eSBhbmQgcnNxIGludG8gZWFjaCBjb2x1bW4gYW5kIHJvdyBpbiBtYXRyaXggZm9yIGVhY2ggcGFyYW1ldGVyIGFuZCBtb2RlbCB0eXBlDQogICAgDQogICAgIyMgcmFuayBvbiBiZXN0IGZpdA0KICAgICMjIHRhYmxlIGZvciByZXBvcnQgb2Ygc3RhdHMgb24gbW9kZWxzDQogICAgDQp9DQoNCiMjIHRha2UgYmVzdCBtb2RlbCBmb3IgZWFjaCBwYXJhcm1ldGVyIGludG8gc2ltdWxhdGlvbg0KIyMgdHJ5IHZlbCBtZWFuIGFuZCBzZCwgYW5kIHR1cm4gbWVhbiBhbmQgc2QgDQoNCnJmX3ByZWRfdHJhaW4gPC0gcHJlZGljdChyZl9maXRfdHJhaW4sIHJmX3Rlc3QpICMjIG5lZWQgdG8gcHJlZGljdCBlYWNoIHBhcmFtZXRlciB3aXRoaW4gc2ltdWxhdGlvbg0KDQojIyBlbnZpcm9ubWVudGFsIGlucHV0IHRvIHN3aW1taW5nLCBlZy4gbm9kZSBwdXJpdHkNCg0KDQoNCmMudiA8LSBhcy5kYXRhLmZyYW1lKGMudikNCmMudl90cmFpbiA8LSBhcy5kYXRhLmZyYW1lKGMudl90cmFpbikNCnJzcTIgPC0gYXMuZGF0YS5mcmFtZShyc3EyKQ0KDQpub2RlLnB1cml0eSRjLmUgPC0gYy52JFYxDQpub2RlLnB1cml0eSRjLmUudCA8LSBjLnZfdHJhaW4kY29yDQpub2RlLnB1cml0eSRyLnNxdWFyZWQgPC0gcnNxMiRWMQ0KDQpgYGANCg0KTm9kZSBQdXJpdHkgaGVhdCBtYXANCmBgYHtyfQ0KIyMjIyMgRXhhbXBsZSBkYXRhDQpzZXQuc2VlZCgxMjMpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFNldCBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkNCmRhdGE8LSBtYXRyaXgocm5vcm0oMTAwLCAwLCAxMCksIG5yb3cgPSAxMCwgbmNvbCA9IDEwKSAgICAgICAgICAgIyBDcmVhdGUgZXhhbXBsZSBkYXRhICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBBcHBseSBoZWF0bWFwIGZ1bmN0aW9uDQoNCmNvbG5hbWVzKGRhdGEpPC0gcGFzdGUwKCJjb2wiLCAxOjEwKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBDb2x1bW4gbmFtZXMNCnJvd25hbWVzKGRhdGEpPC0gcGFzdGUwKCJyb3ciLCAxOjEwKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBSb3cgbmFtZXMNCmhlYWQoZGF0YSw1KQ0KDQpub2RlIDwtIHJlYWQuY3N2KCJDOlxcVXNlcnNcXE5pY29sZSBIZWxsZXNzZXlcXERvY3VtZW50c1xcQmlnZWxvd1xcRGF0YVxcTm9kZSBQdXJpdHkgVmFsdWVzIC0gT3ZlcmFsbCBQcmVkaWN0b3JzLmNzdiIsIGhlYWRlciA9IFQpDQpoZWFkKG5vZGUpDQpub2RlMiA8LSBkYXRhLm1hdHJpeChub2RlKQ0KDQojIyMjI25vZGUjIyMjIyBFeGFtcGxlIDENCmhlYXRtYXAobm9kZTIpICANCg0KIyMjIyMgRXhhbXBsZSAyDQpoZWF0bWFwKG5vZGUyLCBSb3d2ID0gTkEsIENvbHYgPSBOQSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBSZW1vdmUgZGVuZG9ncmFtDQoNCiMjIyMjIEV4YW1wbGUgMw0KbXlfY29sb3JzPC0gY29sb3JSYW1wUGFsZXR0ZShjKCJjeWFuIiwgImRlZXBwaW5rMyIpKSAgICAgICAgICAgICAjIE1hbnVhbCBjb2xvciByYW5nZQ0KaGVhdG1hcChub2RlMiwgY29sID0gbXlfY29sb3JzKDEwMCkpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgSGVhdG1hcCB3aXRoIG1hbnVhbCBjb2xvcnMNCg0KIyMjIyMgRXhhbXBsZSA0ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEluc3RhbGwgcmVzaGFwZSBwYWNrYWdlDQpsaWJyYXJ5KHJlc2hhcGUpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBMb2FkIHJlc2hhcGUgcGFja2FnZQ0KDQpub2RlX21lbHQgPC0gbWVsdChub2RlKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFJlb3JkZXIgZGF0YQ0KbGlicmFyeShnZ3Bsb3QyKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgTG9hZCBnZ3Bsb3QyIHBhY2thZ2UNCg0KZ2dwIDwtIGdncGxvdChub2RlX21lbHQsIGFlcyhGYWN0b3IsIHZhcmlhYmxlKSkgKyAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQ3JlYXRlIGhlYXRtYXAgd2l0aCBnZ3Bsb3QyDQogIGdlb21fdGlsZShhZXMoZmlsbCA9IHZhbHVlKSkNCmdncCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgUHJpbnQgaGVhdG1hcA0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkocmVzaGFwZTIpDQoNCmF0dGFjaChub2RlX21lbHQpDQoNCmdncGxvdChub2RlX21lbHQsIGFlcyh4ID0gRmFjdG9yLCB5ID0gdmFyaWFibGUsIGZpbGwgPSB2YWx1ZSkpICsNCiAgZ2VvbV90aWxlKCkgKw0KICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJ3aGl0ZSIsIGhpZ2ggPSAicmVkIikgKw0KICBsYWJzKHggPSAiRW52aXJvbm1lbnRhbCBDb25kaXRpb24iLCB5ID0gIlN3aW1taW5nIFBhcmFtZXRlciIsIHRpdGxlID0gIk5vZGUgUHVyaXR5IEhlYXRtYXAgLSBPdmVyYWxsIFByZWRpY3RvcnMiKQ0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCiMgbG9hZCByZXF1aXJlZCBwYWNrYWdlcw0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KHJlc2hhcGUyKQ0KDQojIGNyZWF0ZSBoZWF0bWFwIHVzaW5nIHBsb3RseQ0KcGxvdF9seShub2RlX21lbHQsIHggPSBGYWN0b3IsIHkgPSB2YXJpYWJsZSwgeiA9IHZhbHVlLCB0eXBlID0gImhlYXRtYXAiKSAlPiUNCiAgbGF5b3V0KHRpdGxlID0gIkhlYXRtYXAiLCB4YXhpcyA9IGxpc3QodGl0bGUgPSAiQ29sdW1uIiksIHlheGlzID0gbGlzdCh0aXRsZSA9ICJSb3ciKSkNCmBgYA0KDQoNCmBgYHtyfQ0KICAjIyMgdG8gc2F2ZSBlYWNoIHBsb3QgYXMgYW4gaW5kaXZpZHVhbCBncmFwaA0KICAjanBlZyhmaWxlbmFtZT0gcGFzdGUoJ34vQmlnZWxvdy9GaWd1cmVzL3RpZHltb2RlbHMgT3V0cHV0L2ZpdCcsIGksJy5qcGVnJywgc2VwID0gJycpLCB3aWR0aCA9IDk2MCwgICAgICBoZWlnaHQgPSA3ODApDQogICNwbG90KGRmJHJlc3BvbnNlLCByZl9wcmVkJC5wcmVkLCBtYWluID0gcGFzdGUoImNvcnIgPSAiLCBjb3IkZXN0aW1hdGUpKSAgIyMgb2JzZXJ2ZWQgdnMgcHJlZGljdGVkDQogICNkZXYub2ZmKCkNCg0KDQogICNiZXN0X3RyZWUgPC0gcmYuc2tpbGwudGVzdChkZiA9IGRmLCB0cmVlcyA9IDIwMDAsICJhdmUudiIsIHByb3AgPSAwLjUsIHN0cmF0YSA9IE5VTEwsIGRvLnBsb3QgPSBUUlVFKSAjIyBub3Qgd29ya2luZy4uLi4NCiAgI2J0IDwtIHJiaW5kKGJlc3RfdHJlZSwgYnQpDQoNCiMjIGxvb2sgYXQgdGhpbmdzIG91dHNpZGUgcmFuZG9tIGZvcmVzdA0KDQoNCnJmX3RyYWluaW5nX3ByZWQgPC0gDQogIHByZWRpY3QocmZfZml0LCByZl90cmFpbikgJT4lIA0KICBiaW5kX2NvbHMocHJlZGljdChyZl9maXQsIHJmX3RyYWluLCB0eXBlID0gIm51bWVyaWMiKSkgJT4lIA0KICAjIEFkZCB0aGUgdHJ1ZSBvdXRjb21lIGRhdGEgYmFjayBpbg0KICBiaW5kX2NvbHMocmZfdHJhaW4gJT4lIA0KICAgICAgICAgICAgICBzZWxlY3QoZmxvdywgY2hsLCBndWFubywgbGlnaHQpKQ0KDQpyZl90cmFpbmluZ19wcmVkJGxpZ2h0IDwtIGFzLmZhY3RvcihyZl90cmFpbmluZ19wcmVkJGxpZ2h0KQ0KcmZfdHJhaW5pbmdfcHJlZCRndWFubyA8LSBhcy5mYWN0b3IocmZfdHJhaW5pbmdfcHJlZCRndWFubykNCg0KcmZfdHJhaW5pbmdfcHJlZCAlPiUgICAgICAgICAgICAgICAgIyB0cmFpbmluZyBzZXQgcHJlZGljdGlvbnMNCiAgcm9jX2F1Yyh0cnV0aCA9IGxpZ2h0LCAucHJlZC4uLjEpDQoNCnJmX3RyYWluaW5nX3ByZWQgJT4lICAgICAgICAgICAgICAgICMgdHJhaW5pbmcgc2V0IHByZWRpY3Rpb25zLCBvbmx5IHdvcmtzIGZvciBmYWN0b3JzDQogIGFjY3VyYWN5KHRydXRoID0gYXZlLnYsIC5wcmVkLi4uMikNCg0KIyMgbm93IHRoYXQgdGhlIG1vZGVsIGhhcyBleGNlcHRpb25hbCBwZXJmb3JtYW5jZSBsZXRzIG1vdmUgdG8gdGhlIHRlc3QgZGF0YXNldA0KDQpyZl90ZXN0aW5nX3ByZWQgPC0gDQogIHByZWRpY3QocmZfZml0LCByZl90ZXN0KSAlPiUgDQogIGJpbmRfY29scyhwcmVkaWN0KHJmX2ZpdCwgcmZfdGVzdCwgdHlwZSA9ICJudW1lcmljIikpICU+JSANCiAgYmluZF9jb2xzKHJmX3Rlc3QgJT4lIHNlbGVjdChmbG93LCBjaGwsIGd1YW5vLCBsaWdodCkpDQoNCnJmX3Rlc3RpbmdfcHJlZCRsaWdodCA8LSBhcy5mYWN0b3IocmZfdGVzdGluZ19wcmVkJGxpZ2h0KQ0KcmZfdGVzdGluZ19wcmVkJGd1YW5vIDwtIGFzLmZhY3RvcihyZl90ZXN0aW5nX3ByZWQkZ3Vhbm8pDQoNCnJmX3Rlc3RpbmdfcHJlZCAlPiUgICAgICAgICAgICAgICAgICAgIyB0ZXN0IHNldCBwcmVkaWN0aW9ucw0KICByb2NfYXVjKHRydXRoID0gbGlnaHQsIC5wcmVkLi4uMSkNCg0KcmZfdGVzdGluZ19wcmVkICU+JSAgICAgICAgICAgICAgICAgICAjIHRlc3Qgc2V0IHByZWRpY3Rpb25zDQogIGFjY3VyYWN5KHRydXRoID0gbGlnaHQsIGd1YW5vKQ0KDQojIyBkaWZmZXJlbmNlcyBjYXVzZWQgYnkgdHJhaW5pbmcgc2V0IGVycm9yIChiaWFzKSBieSBtb2RlbA0KDQojIyMjIyMgcmVzYW1wbGluZyB0byB0aGUgcmVzY3VlDQoNCnNldC5zZWVkKDM0NSkNCmZvbGRzIDwtIHZmb2xkX2N2KHJmX3RyYWluLCB2ID0gMTApDQpmb2xkcw0KDQpyZl93ZiA8LSAjIyBidW5kbGVzIHdvcmtmbG93IGFuZCByYW5kb20gZm9yZXN0IG1vZGVsIHRvZ2V0aGVyIHdpdGhvdXQgYSByZWNpcGUgbmVlZGVkDQogIHdvcmtmbG93KCkgJT4lDQogIGFkZF9tb2RlbChyZl9tb2QpICU+JQ0KICBhZGRfZm9ybXVsYShhdmUudiB+IC4pDQoNCnNldC5zZWVkKDQ1NikNCnJmX2ZpdF9ycyA8LSANCiAgcmZfd2YgJT4lIA0KICBmaXRfcmVzYW1wbGVzKGZvbGRzKSAgIyNmaXRzIHJlc2FtcGxlcw0KDQpyZl9maXRfcnMgICMjIC5tZXRyaWNzIGNvbHVtbiBjb250YWlucyBtZXRyaWNzIG9uIG1vZGVsIHBlcmZvcm1hbmNlDQoNCmNvbGxlY3RfbWV0cmljcyhyZl9maXRfcnMpICAjI21hbnVhbGx5IHVubmVzdHMgbWV0ZXJpY3MgZGF0YQ0KDQpyZl90ZXN0aW5nX3ByZWQgJT4lICAgICAgICAgICAgICAgICAgICMgdGVzdCBzZXQgcHJlZGljdGlvbnMgKEFTIEFCT1ZFKQ0KICByb2NfYXVjKHRydXRoID0gbGlnaHQsIC5wcmVkLi4uMSkNCg0KcmZfdGVzdGluZ19wcmVkICU+JSAgICAgICAgICAgICAgICAgICAjIHRlc3Qgc2V0IHByZWRpY3Rpb25zICAoQVMgQUJPVkUpDQogIGFjY3VyYWN5KHRydXRoID0gbGlnaHQsIGd1YW5vKQ0KYGBgDQpUdW5pbmcgdGhlIG1vZGVsDQpgYGB7cn0NCmxpYnJhcnkoZ2xtbmV0KQ0KbGlicmFyeShycGFydC5wbG90KSAgIyBmb3IgdmlzdWFsaXppbmcgYSBkZWNpc2lvbiB0cmVlDQpsaWJyYXJ5KHZpcCkgICAgICAgICAjIGZvciB2YXJpYWJsZSBpbXBvcnRhbmNlIHBsb3RzDQoNCnR1bmVfc3BlYyA8LSANCiAgZGVjaXNpb25fdHJlZSggICMjIHRoaXMgaXMgdGhlIHR5cGUgb2YgbW9kZWwNCiAgICBjb3N0X2NvbXBsZXhpdHkgPSB0dW5lKCksDQogICAgdHJlZV9kZXB0aCA9IHR1bmUoKQ0KICApICU+JSANCiAgc2V0X2VuZ2luZSgicnBhcnQiKSAlPiUgDQogIHNldF9tb2RlKCJyZWdyZXNzaW9uIikNCg0KdHVuZV9zcGVjDQoNCnRyZWVfZ3JpZCA8LSBncmlkX3JlZ3VsYXIoY29zdF9jb21wbGV4aXR5KCksDQogICAgICAgICAgICAgICAgICAgICAgICAgIHRyZWVfZGVwdGgoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gNSkNCg0KdHJlZV9ncmlkDQoNCnRyZWVfZ3JpZCAlPiUgIyMgc2hvd3MgZWFjaCBsZXZlbCB3ZSB3aWxsIHR1bmUgdGhlIG1vZGVsIGF0DQogIGNvdW50KHRyZWVfZGVwdGgpDQoNCnNldC5zZWVkKDIzNCkgIyMgZG9uJ3QgdW5kZXJzdGFuZCB3aGF0IHRoZXNlIGRvPz8NCnJmX2ZvbGRzIDwtIHZmb2xkX2N2KHJmX3RyYWluKSAgIyMgY3JlYXRlcyBjcm9zcy12YWxpZGF0aW9uIGZvbGRzIGZvciB0dW5pbmcNCg0Kc2V0LnNlZWQoMzQ1KQ0KDQp0cmVlX3dmIDwtIHdvcmtmbG93KCkgJT4lICAjI2NyZWF0ZXMgdGhlIHdvcmtmbG93DQogIGFkZF9tb2RlbCh0dW5lX3NwZWMpICU+JQ0KICBhZGRfZm9ybXVsYShhdmUudiB+IC4pDQoNCnRyZWVfcmVzIDwtICMjIHJlc2FtcGxlcyBhbmQgdHVuZXMgbW9kZWwNCiAgdHJlZV93ZiAlPiUgDQogIHR1bmVfZ3JpZCgNCiAgICByZXNhbXBsZXMgPSByZl9mb2xkcywNCiAgICBncmlkID0gdHJlZV9ncmlkDQogICAgKQ0KDQp0cmVlX3JlcyAgIyMgZ2l2ZXMgdHVuaW5nIHJlc3VsdHMNCg0KdHJlZV9yZXMgJT4lIA0KICBjb2xsZWN0X21ldHJpY3MoKSAgIyMgY29sbGVjdHMgbWV0cmljcyBmcm9tIHR1bmVkIG1vZGVscw0KDQoNCnRyZWVfcmVzICU+JQ0KICBjb2xsZWN0X21ldHJpY3MoKSAlPiUNCiAgbXV0YXRlKHRyZWVfZGVwdGggPSBmYWN0b3IodHJlZV9kZXB0aCkpICU+JQ0KICBnZ3Bsb3QoYWVzKGNvc3RfY29tcGxleGl0eSwgbWVhbiwgY29sb3IgPSB0cmVlX2RlcHRoKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuNSwgYWxwaGEgPSAwLjYpICsNCiAgZ2VvbV9wb2ludChzaXplID0gMikgKw0KICBmYWNldF93cmFwKH4gLm1ldHJpYywgc2NhbGVzID0gImZyZWUiLCBucm93ID0gMikgKw0KICBzY2FsZV94X2xvZzEwKGxhYmVscyA9IHNjYWxlczo6bGFiZWxfbnVtYmVyKCkpICsNCiAgc2NhbGVfY29sb3JfdmlyaWRpc19kKG9wdGlvbiA9ICJwbGFzbWEiLCBiZWdpbiA9IC45LCBlbmQgPSAwKQ0KDQojIyBzdHViYmllc3QgdHJlZSB3aXRoIGEgZGVwdGggb2YgMSBwZXJmb3JtZWQgdGhlIHdvcnN0DQojIyBkZWVwZXN0IHRyZWUgd2l0aCBkZXB0aCBvZiAxNSBkaWQgYmV0dGVyDQoNCnRyZWVfcmVzICU+JQ0KICBzaG93X2Jlc3QobWV0cmljID0gInJtc2UiKSAgIyMgc2hvd3MgYmVzdCBtb2RlbCBmaXQNCg0KYmVzdF90cmVlIDwtIHRyZWVfcmVzICU+JSAgIyMgcHVsbHMgb3V0IGRhdGEgb24gdGhlIGJlc3QgZml0DQogIHNlbGVjdF9iZXN0KG1ldHJpYyA9ICJybXNlIikNCg0KYmVzdF90cmVlICMjIHN1bW1hcnkgb2YgYmVzdCB0cmVlIG1vZGVsDQoNCmZpbmFsX3dmIDwtICMjIGNyZWF0ZSB3b3JrZmxvdyBmcm9tIGJlc3QgdHJlZSBtb2RlbCBhZnRlciB0dW5pbmcNCiAgdHJlZV93ZiAlPiUgDQogIGZpbmFsaXplX3dvcmtmbG93KGJlc3RfdHJlZSkNCg0KZmluYWxfd2YNCg0KZmluYWxfZml0IDwtICMjIGNyZWF0ZSBmaW5hbCBtb2RlbCBmcm9tIG5ldyBmaXQNCiAgZmluYWxfd2YgJT4lDQogIGxhc3RfZml0KHJmX3NwbGl0KSANCg0KZmluYWxfZml0ICU+JQ0KICBjb2xsZWN0X21ldHJpY3MoKQ0KDQoNCmZpbmFsX2ZpdCAlPiUgICMjIHBsb3QgUk9DIGFuZCBjb21wYXJlIHBlcmZvcm1hbmNlIGFmdGVyIHR1bmluZw0KICBjb2xsZWN0X3ByZWRpY3Rpb25zKCkgJT4lIA0KICByb2NfY3VydmUoZmxvdywgYXZlLnYpICU+JSAjIyMgTk9UIFdPUktJTkcNCiAgYXV0b3Bsb3QoKQ0KDQpmaW5hbF90cmVlIDwtIGV4dHJhY3Rfd29ya2Zsb3coZmluYWxfZml0KSAgIyMgZXh0cmFjdCBvdXIgZmluYWwgZml0IGZvciBmdXR1cmUgdXNlDQpmaW5hbF90cmVlDQoNCmZpbmFsX3RyZWUgJT4lICAjIyBjcmVhdGVzIHdvcmtmbG93IHBsb3QNCiAgZXh0cmFjdF9maXRfZW5naW5lKCkgJT4lDQogIHJwYXJ0LnBsb3Qocm91bmRpbnQgPSBGQUxTRSkNCg0KZmluYWxfdHJlZSAlPiUgIyMgc2hvd3Mgd2hpY2ggdmFyaWFibGVzIGFyZSBtb3N0IGltcG9ydGFudCB0byB0aGUgbW9kZWwgaW4gYSBwbG90DQogIGV4dHJhY3RfZml0X3BhcnNuaXAoKSAlPiUgDQogIHZpcCgpDQoNCmFyZ3MoZGVjaXNpb25fdHJlZSkNCg0KDQpgYGANCkJpZ2dlciBSRiBNb2RlbA0KYGBge3J9DQpjb3JlcyA8LSBwYXJhbGxlbDo6ZGV0ZWN0Q29yZXMoKSAjIyBzZWVzIGhvdyBtYW55IGNvcmVzIHdlIGhhdmUgdG8gcHJvY2VzcyB0aGUgZGF0YQ0KY29yZXMNCg0KcmZfbW9kIDwtICMjIHJhbmRvbSBmb3Jlc3QgbW9kZWwgZ2VuZXJhdGlvbiwgcGFyYWxsZWwgcHJvY2Vzc2luZyBvZiBtb2RlbHMNCiAgcmFuZF9mb3Jlc3QobXRyeSA9IHR1bmUoKSwgbWluX24gPSB0dW5lKCksIHRyZWVzID0gMTAwMCkgJT4lIA0KICBzZXRfZW5naW5lKCJyYW5nZXIiLCBudW0udGhyZWFkcyA9IGNvcmVzKSAlPiUgDQogIHNldF9tb2RlKCJyZWdyZXNzaW9uIikNCg0KcmZfcmVjaXBlIDwtICMjIGNyZWF0ZSByYW5kb20gZm9yZXN0IG1vZGVsIHJlY2lwZQ0KICByZWNpcGUoYXZlLnYgfiAuLCBkYXRhID0gZGYpIA0KICANCnJmX3dvcmtmbG93IDwtICMjIGNyZWF0ZSByYW5kb20gZm9yZXN0IG1vZGVsIHdvcmtmbG93DQogIHdvcmtmbG93KCkgJT4lIA0KICBhZGRfbW9kZWwocmZfbW9kKSAlPiUgDQogIGFkZF9yZWNpcGUocmZfcmVjaXBlKQ0KDQpyZl9tb2QNCg0KdmFsX3NldCA8LSB2YWxpZGF0aW9uX3NwbGl0KGRmLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJhdGEgPSBmbG93LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9wID0gMC44MCkNCnZhbF9zZXQNCiMgc2hvdyB3aGF0IHdpbGwgYmUgdHVuZWQNCmV4dHJhY3RfcGFyYW1ldGVyX3NldF9kaWFscyhyZl9tb2QpDQoNCnNldC5zZWVkKDM0NSkNCnJmX3JlcyA8LSANCiAgcmZfd29ya2Zsb3cgJT4lIA0KICB0dW5lX2dyaWQodmFsX3NldCwNCiAgICAgICAgICAgIGdyaWQgPSAyNSwNCiAgICAgICAgICAgIGNvbnRyb2wgPSBjb250cm9sX2dyaWQoc2F2ZV9wcmVkID0gVFJVRSksDQogICAgICAgICAgICBtZXRyaWNzID0gbWV0cmljX3NldChybXNlKSkNCg0KcmZfcmVzICU+JSAjIyBzaG93cyA1IGJlc3QgcmFuZG9tIGZvcmVzdCBtb2RlbHMgb3V0IG9mIHRoZSAyNSBjYW5kaWRhdGVzDQogIHNob3dfYmVzdChtZXRyaWMgPSAicm1zZSIpDQoNCmF1dG9wbG90KHJmX3JlcykgICMjIHBsb3QgcmVzdWx0cw0KDQpgYGANCg0KYGBge3J9DQpyZl9iZXN0IDwtICMjIGNyZWF0ZXMgbW9kZWwgd2l0aCBiZXN0IHByZWRpY3RvcnMNCiAgcmZfcmVzICU+JSANCiAgc2VsZWN0X2Jlc3QobWV0cmljID0gInJtc2UiKQ0KcmZfYmVzdA0KDQpyZl9yZXMgJT4lICMjIGNvbGxlY3RzIGRhdGEgZm9yIFJPQyBjdXJ2ZSBwbG90DQogIGNvbGxlY3RfcHJlZGljdGlvbnMoKQ0KDQpyZl9iZXN0JG10cnkgPC0gYXMuaW50ZWdlcihyZl9iZXN0JG10cnkpDQoNCg0KIyNOT1QgV09SS0lORw0KcmZfYXVjIDwtICMjIGNyZWF0ZXMgc2V0IG9mIG1vZGVscyB3aXRoIGJlc3QgbW9kZWwgYW5kIG1vZGVsIG1vZGVsIGZvciBjb21wYXJpc29uDQogIHJmX3JlcyAlPiUgDQogIGNvbGxlY3RfcHJlZGljdGlvbnMocGFyYW1ldGVycyA9IHJmX2Jlc3QpICU+JSANCiAgcm9jX2N1cnZlKG10cnksIC5wcmVkKSAlPiUgDQogIG11dGF0ZShtb2RlbCA9ICJSYW5kb20gRm9yZXN0IikgIA0KDQojI05PVCBXT1JLSU5HDQpiaW5kX3Jvd3MocmZfYmVzdCwgcmZfcmVzKSAlPiUgIyMgcGxvdHMgbW9kZWwgY29tcGFyaXNvbnMgb24gUk9DIGN1cnZlDQogIGdncGxvdChhZXMoeCA9IDEgLSBzcGVjaWZpY2l0eSwgeSA9IHNlbnNpdGl2aXR5LCBjb2wgPSBtb2RlbCkpICsgDQogIGdlb21fcGF0aChsd2QgPSAxLjUsIGFscGhhID0gMC44KSArDQogIGdlb21fYWJsaW5lKGx0eSA9IDMpICsgDQogIGNvb3JkX2VxdWFsKCkgKyANCiAgc2NhbGVfY29sb3JfdmlyaWRpc19kKG9wdGlvbiA9ICJwbGFzbWEiLCBlbmQgPSAuNikNCmBgYA0KDQpgYGB7cn0NCiMjIyMjIyMjIyMjIyMjIyMgbGFzdCBtb2RlbCBhZnRlciB0dW5pbmcgDQoNCiMgdGhlIGxhc3QgbW9kZWwNCmxhc3RfcmZfbW9kIDwtIA0KICByYW5kX2ZvcmVzdChtdHJ5ID0gOCwgbWluX24gPSA3LCB0cmVlcyA9IDEwMDApICU+JSANCiAgc2V0X2VuZ2luZSgicmFuZ2VyIiwgbnVtLnRocmVhZHMgPSBjb3JlcywgaW1wb3J0YW5jZSA9ICJpbXB1cml0eSIpICU+JSANCiAgc2V0X21vZGUoInJlZ3Jlc3Npb24iKQ0KDQojIHRoZSBsYXN0IHdvcmtmbG93DQpsYXN0X3JmX3dvcmtmbG93IDwtIA0KICByZl93b3JrZmxvdyAlPiUgDQogIHVwZGF0ZV9tb2RlbChsYXN0X3JmX21vZCkNCg0KIyB0aGUgbGFzdCBmaXQNCnNldC5zZWVkKDM0NSkNCmxhc3RfcmZfZml0IDwtIA0KICBsYXN0X3JmX3dvcmtmbG93ICU+JSANCiAgbGFzdF9maXQocmZfc3BsaXQpDQoNCmxhc3RfcmZfZml0DQoNCmxhc3RfcmZfZml0ICU+JSAgIyMgY29sbGVjdCBtZXRyaWNzIGZyb20gZmluYWwgbW9kZWwNCiAgY29sbGVjdF9tZXRyaWNzKCkNCg0KbGFzdF9yZl9maXQgJT4lICMjIHVwZGF0ZXMgbW9kZWwgZml0DQogIGV4dHJhY3RfZml0X3BhcnNuaXAoKSAlPiUgDQogIHZpcChudW1fZmVhdHVyZXMgPSAyMCkNCg0KIyNOT1QgV09SS0lORw0KbGFzdF9yZl9maXQgJT4lICMjIHBsb3RzIGJlc3QgUk9DIGN1cnZlLCB3aXRoIGJlc3Qgc2V0IG9mIGh5cGVycGFyYW1ldGVycyBhcyBwcmVkaWN0b3JzDQogIGNvbGxlY3RfcHJlZGljdGlvbnMoKSAlPiUgDQogIHJvY19jdXJ2ZShhdmUudiwgLnByZWQuLi4xKSAlPiUgDQogIGF1dG9wbG90KCkNCg0KYGBgDQoNCg==